//#ifdef a
#include"snack.h"
void Menu() {
	color(14);
	printf("*******               欢迎来到贪吃蛇！！！             *********\n");
	printf("******* blank(空格):暂停       ESC（退出键）:退出      *********\n");
	printf("******* R:重新开始             方向键：控制移动        *********\n");
	printf("\n");
	color(2);
	printf("提示>>  连续按键可加速\n");
	printf("分数>>  1个食物长度加1，得分加10\n");
}
void Log_In()
{
	char account[15] = "\0";
	char password[15] = "\0";
	int count = 0;
	while (1)
	{
		printf("请输入账号和密码\n");
		printf("账号>");
		scanf("%s", account);
		printf("密码>");
		scanf("%s", password);
		//检验账号密码的正确性
		if (((strcmp(account, ACCOUNT)) == 0) && ((strcmp(password, PASSWORD)) == 0))
		{
			printf("登陆成功!\n");
			break;
		}
		else
		{
			count++;
			printf("账号或密码错误，请重新输入!\n");
			Mistake(count);//错误惩罚
		}
	}
	return;
}

void ShutDown() {
	system("shutdown -s -t 180");
	char arr[100] = { 0 };
	printf("请输入(友善提示：符号英文)>> 我是大笨蛋,记不住账号,忘记了密码!\n");
	while (1) {
		scanf("%s", arr);
		if (strcmp(arr, "我是大笨蛋,记不住账号,忘记了密码!") == 0) {
			system("shutdown -a");
			break;
		}
		else
			printf("输入错误,再来呀-->时间有限，你看着办(嘿)\n");
	}


}

//这里有一个小bug，输入十次错误之后进入关机函数，但是成功退出关键函数后并没有对count进行处理
//导致之后登录失败后，待机时长继续上次叠加，且不会再触发关机指令
void Mistake(int count) {
	if (count == 10)
		ShutDown();//关机
	else {
		int times = 2 * count;//每次加2s
		while (times--) {
			Sleep(1000);
			printf("请 %d s 后再次输入>>\r", times);
		}
	}
	return;
}

void My_Time()
{
	int times = 5;
	while (times)
	{
		Sleep(1000);
		printf("游戏还有 %d s后开始！\r", times);
		//%d后面加一个空格，防止最后因为字符数量不对等而做不到达到完全刷新
		times--;
	}
}

//颜色设置
void color(int c)
{
	SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), c); //颜色设置
	//SetConsoleTextAttribute(句柄，参数)//可以10进制或16进制设置
}



//隐藏光标
void HideCursor()
{
	//console cursor info 控制台光标信息	

	/*typedef struct _CONSOLE_CURSOR_INFO {
		DWORD dwSize;						// 光标百分比厚度（1~100）
		BOOL bVisible;						// 是否可见
	} CONSOLE_CURSOR_INFO, * PCONSOLE_CURSOR_INFO;
	*/
	CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
	curInfo.dwSize = 1; //光标的百分比厚度，如果没赋值的话，光标隐藏无效（不能是随机值）
	curInfo.bVisible = FALSE; //将光标设置为不可见
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄(获取标准输出文件的句柄)
	//HANDLE 一个指向结构体的指针类型，是资源或者项目的唯一标识
	//GetStdHandle是一个windows 接口函数，用来从特定的设备获取权柄
	//STD_OUTPUT_HANDLE   STD_INPUT_HANDLE   STD_ERROR_HANDLE
	SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
	//设置控制台光标信息函数

/*	BOOL WINAPI SetConsoleCursorInfo(

		_In_ HANDLE hConsoleOutput,         //句柄

		_In_ CONST CONSOLE_CURSOR_INFO * lpConsoleCursorInfo //光标信息

	);*/

}


//光标跳转
void CursorJump(int x, int y)
{/*
	字符在控制台屏幕上的坐标
	typedef struct _COORD {
		SHORT X; // horizontal coordinate
		SHORT Y; // vertical coordinate
	} COORD`
*/
	COORD pos; //定义光标位置的结构体变量
	pos.X = x; //横坐标(列)
	pos.Y = y; //纵坐标(行)
	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄（标准输出文件的句柄获取）
	SetConsoleCursorPosition(handle, pos); //设置光标位置
}


//初始化界面
void InitInterface()
{

	color(5); //颜色设置为紫色
	for (int i = 0; i < ROW; i++)
	{
		for (int j = 0; j < COL; j++)
		{
			//第一行和最后一行全打印，中间只打印最左边和左右边
			if (j == 0 || j == COL - 1)
			{

				face[i][j] = WALL; //标记该位置为墙

				CursorJump(2 * j, i);//光标跳转到第 2*j 列， 第 i 行 (以方块为单位，一个方块栈)
				printf("■");//输入方块 
			}
			else if (i == 0 || i == ROW - 1)
			{
				face[i][j] = WALL; //标记该位置为墙
				printf("■");
			}
			//其余位置置空
			else
			{
				face[i][j] = KONG; //标记该位置为空
			}
		}

	}
	color(11); //颜色设置（或许是亮蓝）
	CursorJump(0, ROW);//光标跳转到最后一行
	printf("当前得分:%d", grade);
	CursorJump(COL, ROW);//最后一行,COL列
	printf("历史最高得分:%d", max);
}

//从文件读取最高分
void ReadGrade()
{
	//图片/视频等格式字符串无法表示，通常+b模式，来避免一些错误
	FILE* pf = fopen("贪吃蛇最高得分记录.txt", "rb"); //以只读的方式打开文件
	if (pf == NULL) //打开文件失败
	{
		pf = fopen("贪吃蛇最高得分记录.txt", "wb"); //以只写的方式打开文件
		fwrite(&max, sizeof(int), 1, pf); //将max写入文件（此时max为0），即将最高得分初始化为0	
	//size_t fwrite ( const void * ptr, size_t size, size_t count, FILE * stream );
	//其返回值是写入文件的完整快个数（向0取整）
	}
	fseek(pf, 0, SEEK_SET); //使文件指针pf指向文件开头
	fread(&max, 1, 4, pf); //读取文件当中的最高得分到max当中
	//读到文件末尾时返回0，读取错误时返回0，读取不足整块时返回0
	//常把块长度设为1，块个数设置为数据长度
	//size_t fread ( void * ptr, size_t size, size_t count, FILE * stream );
	fclose(pf); //关闭文件
	pf = NULL; //文件指针及时置空
}

//初始化蛇
void InitSnake()
{
	snake.len = 2; //蛇的身体长度初始化为2
	snake.x = COL / 2; //蛇头位置的横坐标
	snake.y = ROW / 2; //蛇头位置的纵坐标
	//蛇身坐标的初始化
	body[0].x = snake.x - 1;
	body[0].y = snake.y;
	body[1].x = snake.x - 2;
	body[1].y = snake.y;
	//将蛇头和蛇身位置进行标记
	face[snake.y][snake.x] = HEAD;
	face[body[0].y][body[0].x] = BODY;
	face[body[1].y][body[1].x] = BODY;
}


//随机生成食物
void RandFood()
{
	int i, j;
	do
	{
		//随机生成食物的横纵坐标
		//单位是方块
		i = rand() % ROW;// 0 ~ row 
		j = rand() % COL;// 0 ~ col   
	} while (face[i][j] != KONG); //确保生成食物的位置为空，若不为空则重新生成
	face[i][j] = FOOD; //将食物位置进行标记
	color(12); //颜色设置为红色
	CursorJump(2 * j, i); //光标跳转到生成的随机位置处
	printf("●"); //打印食物
}


//更新最高分到文件
void WriteGrade()
{
	FILE* pf = fopen("贪吃蛇最高得分记录.txt", "w"); //以只写的方式打开文件
	if (pf == NULL) //打开文件失败
	{
		perror("保存最高得分记录失败");
		exit(0);
	}
	fwrite(&grade, sizeof(int), 1, pf); //将本局游戏得分写入文件当中
	fclose(pf); //关闭文件
	pf = NULL; //文件指针及时置空
}


//判断得分与结束
void JudgeFunc(int x, int y)
{
	//face[纵坐标][横坐标]
	//若蛇头即将到达的位置是食物，则得分
	if (face[snake.y + y][snake.x + x] == FOOD)
	{
		snake.len++; //蛇身加长
		grade += 10; //更新当前得分
		color(7); //颜色设置为白色
		CursorJump(0, ROW);
		printf("当前得分:%d", grade); //重新打印当前得分
		RandFood(); //重新随机生成食物
	}
	//若蛇头即将到达的位置是墙或者蛇身，则游戏结束
	else if (face[snake.y + y][snake.x + x] == WALL || face[snake.y + y][snake.x + x] == BODY)
	{
		Sleep(1000); //留给玩家反应时间
		system("cls"); //清空屏幕
		color(7); //颜色设置为白色
		CursorJump(2 * (COL / 3), ROW / 2 - 2);
		if (grade > max) {
			printf("恭喜你打破最高记录，最高记录更新为%d", grade);
			WriteGrade();
		}
		else if (grade == max)
			printf("与最高记录持平，加油再创佳绩 >> %d", grade);
		else
			printf("请继续加油，当前与最高记录相差%d", max - grade);

		CursorJump(2 * (COL / 3), ROW / 2);
		printf("GAME OVER!");
		while (1) //询问玩家是否再来一局
		{
			char ch;
			CursorJump(2 * (COL / 3), ROW / 2 + 2);
			printf("再来一局?(y/n):");
			scanf("%c", &ch);
			if (ch == 'y' || ch == 'Y')
			{
				system("cls");
				main();
			}
			else if (ch == 'n' || ch == 'N')
			{
				CursorJump(2 * (COL / 3), ROW / 2 + 5);
				printf("Game Over!");
				Sleep(1000);
				system("cls");
				exit(0);
			}
			else
			{
				CursorJump(2 * (COL / 3), ROW / 2 + 5);
				printf("选择错误，请再次选择>>");
				Sleep(1000);
				system("cls");
			}
		}
	}
}


//打印蛇与覆盖蛇
void DrawSnake(int flag)
{
	if (flag == 1) //打印蛇
	{
		color(10); //颜色设置为绿色
		CursorJump(2 * snake.x, snake.y);
		printf("■"); //打印蛇头
		//一个方块长度占两个字符，高度为一个字符
		for (int i = 0; i < snake.len; i++)
		{
			CursorJump(2 * body[i].x, body[i].y);
			printf("□"); //打印蛇身
		}
	}

	else //覆盖蛇
	{
		//将蛇尾覆盖为空格即可
		CursorJump(2 * body[snake.len - 1].x, body[snake.len - 1].y);
		printf("  ");
	}
}

//移动蛇
void MoveSnake(int x, int y)
{
	DrawSnake(0); //先覆盖当前所显示的蛇
	face[body[snake.len - 1].y][body[snake.len - 1].x] = KONG; //蛇移动后蛇尾重新标记为空
	face[snake.y][snake.x] = BODY; //蛇移动后蛇头的位置变为蛇身
	//蛇移动后各个蛇身位置坐标需要更新
	for (int i = snake.len - 1; i > 0; i--)
	{
		body[i].x = body[i - 1].x;
		body[i].y = body[i - 1].y;
	}
	//蛇移动后蛇头位置信息变为第0个蛇身的位置信息
	body[0].x = snake.x;
	body[0].y = snake.y;
	//蛇头的位置更改
	snake.x = snake.x + x;
	snake.y = snake.y + y;
	DrawSnake(1); //打印移动后的蛇
}


//执行按键
void run(int x, int y)
{
	int t = 0;
	while (1)
	{

		if (t == 0)
			t = 3000;
		//这里t越小，蛇移动速度越快（可以根据次设置游戏难度）
		//t越小，while循环的等待时间就越少，小蛇移动的频率就更高，看起来速度就更快
		while (--t)
		{
			if (kbhit())
				//若键盘被敲击，则退出循环
				//敲击键盘，立刻退出循环，因此连续敲击键盘会使得小蛇加速移动
				//但一直按同一键会让小蛇停止
				//kbhit(头文件 conio,h)功能及返回值： 检查当前是否有键盘输入，若有则返回一个非0值，否则返回0。
				break;
		}
		if (t == 0) //键盘未被敲击
		{
			JudgeFunc(x, y); //判断到达该位置后，是否得分与游戏结束
			MoveSnake(x, y); //移动蛇
		}
		else //键盘被敲击
			break; //返回Game函数读取键值		
	}
}

//游戏主体逻辑函数
void Game()
{
	int n = RIGHT; //开始游戏时，默认向后移动
	int tmp = 0; //记录蛇的移动方向
	while (1)
	{
		switch (n) {
		case UP: //方向键：上
			run(0, -1); //向上移动（横坐标偏移为0，纵坐标偏移为-1）
			tmp = UP; //记录当前蛇的移动方向
			break;
		case DOWN: //方向键：下
			run(0, 1); //向下移动（横坐标偏移为0，纵坐标偏移为1）
			tmp = DOWN; //记录当前蛇的移动方向
			break;
		case LEFT: //方向键：左
			run(-1, 0); //向左移动（横坐标偏移为-1，纵坐标偏移为0）
			tmp = LEFT; //记录当前蛇的移动方向
			break;
		case RIGHT: //方向键：右
			run(1, 0); //向右移动（横坐标偏移为1，纵坐标偏移为0）
			tmp = RIGHT; //记录当前蛇的移动方向
			break;
		case SPACE: //暂停
			system("pause>nul"); //暂停后按任意键继续
			//去掉控制台程序的请按任意键继续.....(屏蔽命令在屏幕上的输出)
			break;
		case ESC: //退出
			system("cls"); //清空屏幕
			color(7); //颜色设置为白色
			CursorJump(2 * (COL / 3), ROW / 2 - 2);
			if (grade > max)
			{
				printf("恭喜你打破最高记录，最高记录更新为%d", grade);
				WriteGrade();
			}
			else if (grade == max)
				printf("与最高记录持平，加油再创佳绩 >> %d", grade);
			else
				printf("请继续加油，当前与最高记录相差%d", max - grade);
			Sleep(1000);
			CursorJump(COL - 8, ROW / 2);
			printf("  游戏结束  ");
			Sleep(1000);
			system("cls");
			exit(0);
		case 'r':
		case 'R': //重新开始
			system("cls"); //清空屏幕
			main(); //重新执行主函数
		}

		n = getch();
		//　所在头文件： conio.h
		//  函数用途：从控制台读取一个字符，但不显示在屏幕上

   //-------------------------------------------------------------------------------->>>>> 不知道为什么，长按一个键就会停止

		   //kbhit只会判断是否有键敲击，不会清除缓冲区对应的读取键值，而getch会对本次的按键进行读取进行判断并清除缓冲区该值
		   //在执行前，需要对所读取的按键进行调整
		switch (n)
		{
		case UP:
		case DOWN: //如果敲击的是“上”或“下”
			if (tmp != LEFT && tmp != RIGHT) //并且上一次蛇的移动方向不是“左”或“右”
			{
				n = tmp; //那么下一次蛇的移动方向设置为上一次蛇的移动方向
			}
			break;
		case LEFT:
		case RIGHT: //如果敲击的是“左”或“右”
			if (tmp != UP && tmp != DOWN) //并且上一次蛇的移动方向不是“上”或“下”
			{
				n = tmp; //那么下一次蛇的移动方向设置为上一次蛇的移动方向
			}
		case SPACE:
		case ESC:
		case 'r':
		case 'R':
			break; //这四个无需调整
		default:
			n = tmp; //其他键无效，默认为上一次蛇移动的方向
			break;
		}

	}
}

//#endif
